/*
** DLNA MediaServer
*/

#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/uio.h>

#include <httpd.h>
#include <http_config.h>
#include <http_protocol.h>
#include <http_log.h>
#include <ap_config.h>

#include <apr_strings.h>
#include <apr_fnmatch.h>
#include <apr_tables.h>

#include "config.h"
#include "defs.h"
#include "extattr.h"
#include "template.h"

struct time_range {
	double begin;
	double end;
};
struct byte_range {
	apr_off_t begin;
	apr_off_t end;
};

module AP_MODULE_DECLARE_DATA dlna_video_module;	// fwd decl

static struct time_range
get_range_sec(request_rec *r)
{
	struct time_range tr = { -1.0, 0.0 };

	const char* trange = (const char*)apr_table_get(r->headers_in, "TimeSeekRange.dlna.org");
	if (trange == NULL)
		return tr;

	// TimeSeekRange.dlna.org: npt=60.000-
	char* p = strstr(trange, "npt=");
	if (p == NULL)
		return tr;
	p += 4;

	int n = sscanf(p, "%lf%lf", &tr.begin, &tr.end);
	if (n == 2)
		tr.end = - tr.end;

	return tr;
}

static struct byte_range
get_range_byte(request_rec *r)
{
	struct byte_range br = { -1, 0 };

	const char* crange = (const char*)apr_table_get(r->headers_in, "Range");
	if (crange == NULL)
		return br;

	char* p = strstr(crange, "bytes=");
	if (p == NULL)
		return br;
	p += 6;

	apr_off_t o;
	if (apr_strtoff(&o, p, NULL, 0))
		return br;
	br.begin = o;

	p = strstr(p, "-");
	if (p != NULL) {
		++p;
		if (apr_strtoff(&o, p, NULL, 0) == 0)
			br.end = o;
	}
	return br;
}

static int
stream_out(request_rec *r)
{
	struct exattr exinfo;
	get_extended_attribute(r->pool, &exinfo, r->filename);
	if (exinfo.dc_title == NULL)
		return DECLINED;

	struct time_range tr = get_range_sec(r);
	struct byte_range br = get_range_byte(r);
	int status = OK;
	if (tr.begin >= 0.0 || br.begin >= 0)
		status = HTTP_PARTIAL_CONTENT;

	ap_set_content_type(r, MIME_V);
	apr_table_addn(r->headers_out, "EXT", "");
	apr_table_addn(r->headers_out, "realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*");
	apr_table_addn(r->headers_out, "contentFeatures.dlna.org",
	    "DLNA.ORG_PN=" DLNA_PN ";"
	    "DLNA.ORG_OP=11;"
	    "DLNA.ORG_CI=0;"
	    "DLNA.ORG_FLAGS=61700000000000000000000000000000");

	if (r->header_only)
		return status;

	// ----------------

	apr_file_t* fp;
	apr_status_t st = apr_file_open(&fp, r->filename, APR_FOPEN_READ, APR_FPROT_UREAD, r->pool);
    	if (st) {
		ap_log_rerror(APLOG_MARK, APLOG_ERR, st, r,
		    "fail to open: %s", r->filename);
		return HTTP_NOT_FOUND;
	}

	struct byte_range pos = { 0, 0 };

	if (tr.begin >= 0.0) {
		char* time_range;

		apr_int64_t bitrate = apr_atoi64(exinfo.bitrate); // byte per sec
		pos.begin = (apr_off_t)(bitrate * tr.begin);
		if (tr.end > 0.0) {
			pos.end = (apr_off_t)(bitrate * tr.end);

			time_range = apr_psprintf(r->pool,
			    "npt=%.3f-%.3f bytes %s-%s",
			    tr.begin,
			    tr.end,
			    apr_off_t_toa(r->pool, pos.begin),
			    apr_off_t_toa(r->pool, pos.end));
		} else {
			time_range = apr_psprintf(r->pool,
			    "npt=%.3f- bytes %s-",
			    tr.begin,
			    apr_off_t_toa(r->pool, pos.begin));
		}

		apr_table_addn(r->headers_out, "TimeSeekRange.dlna.org", time_range);
	} else if (br.begin >= 0) {
		char* content_range;

		pos.begin = br.begin;
		if (br.end > 0) {
			pos.end = br.end;

			content_range = apr_psprintf(r->pool,
			    "bytes %s-%s",
			    apr_off_t_toa(r->pool, pos.begin),
			    apr_off_t_toa(r->pool, pos.end));
		} else {
			content_range = apr_psprintf(r->pool,
			    "bytes %s-",
			    apr_off_t_toa(r->pool, pos.begin));
		}

		apr_table_addn(r->headers_out, "Content-Range", content_range);
	}

	apr_size_t sent;
	if (pos.end > 0)
		ap_send_fd(fp, r, pos.begin, pos.end - pos.begin, &sent);
	else
		ap_send_fd(fp, r, pos.begin, r->finfo.size - pos.begin, &sent);

	return status;
}

static int
xml_out(request_rec* r)
{
	if (apr_fnmatch("*/root.xml", r->filename, 0) == APR_SUCCESS) {
		ap_set_content_type(r, "text/xml;charset=\"utf-8\"");

		dlna_dir_config* cfg = (dlna_dir_config*)
		    ap_get_module_config(r->per_dir_config, &dlna_video_module);
		
		ap_rprintf(r, template_root,
		    cfg->name, cfg->serial, cfg->uuid);

		return OK;
	} else if (apr_fnmatch("*/content.xml", r->filename, 0) == APR_SUCCESS) {
		ap_set_content_type(r, "text/xml;charset=\"utf-8\"");

		ap_rputs(template_content, r);
		return OK;
	} else if (apr_fnmatch("*/conn.xml", r->filename, 0) == APR_SUCCESS) {
		ap_set_content_type(r, "text/xml;charset=\"utf-8\"");

		ap_rputs(template_conn, r);
		return OK;
	}

	return DECLINED;
}

static int
dlna_handler(request_rec *r)
{
	if (strcmp(r->handler, "dlna-video-handler")) {
		return DECLINED;
	}

	if (apr_fnmatch("*/control", r->filename, 0) == APR_SUCCESS) {
		if (r->path_info != NULL) {
			if (strcmp(r->path_info, "/content") == 0)
				return dlna_content(r);

			if (strcmp(r->path_info, "/conn") == 0)
				return dlna_conn(r);
		}
	}

	if (apr_fnmatch("*.xml", r->filename, 0) == APR_SUCCESS)
		return xml_out(r);

	return stream_out(r);
}

static void
dlna_register_hooks(apr_pool_t *p)
{
	ap_hook_handler(dlna_handler, NULL, NULL, APR_HOOK_MIDDLE);
//	ap_hook_post_config(register_ssdpd, NULL, NULL, APR_HOOK_MIDDLE);
}

/* Dispatch list for API hooks */
module AP_MODULE_DECLARE_DATA dlna_video_module = {
	STANDARD20_MODULE_STUFF,
	dlna_create_dir_config,/* create per-dir    config structures */
	NULL,                  /* merge  per-dir    config structures */
	NULL,                  /* create per-server config structures */
	NULL,                  /* merge  per-server config structures */
	dlna_cmds,             /* table of config file commands       */
	dlna_register_hooks    /* register hooks                      */
};
